//
//  AQLagMonitor.m
//  RunLoopDemo
//
//  Created by zhangjikuan on 2020/11/20.
//  Copyright © 2020 hsgd. All rights reserved.
//
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define STUCKMONITORRATE 88

#import "AQLagMonitor.h"
#import "SMCallStack.h"
#import "SMCallStackModel.h"

@interface AQLagMonitor ()
{
    int timeoutCount;
    CFRunLoopObserverRef runLoopObserver;
    @public
    dispatch_semaphore_t dispatchSemaphore;
    CFRunLoopActivity runLoopActivity;
}
@property (nonatomic, strong) NSTimer *cpuMonitorTimer;

@end

@implementation AQLagMonitor
#pragma mark - Interface
+ (instancetype)shareInstance {
    static id instance = nil;
    static dispatch_once_t dispatchOnce;
    dispatch_once(&dispatchOnce, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
- (void)beginMonitor {
    self.isMonitoring = YES;
    
    // 监测CPU消耗
    self.cpuMonitorTimer = [NSTimer scheduledTimerWithTimeInterval:3
                                                            target:self
                                                          selector:@selector(updateCPUInfo)
                                                          userInfo:nil
                                                           repeats:YES];
    
    // 监测卡顿
    if (runLoopObserver) {
        return;
    }
    
    dispatchSemaphore = dispatch_semaphore_create(0);
    
    // 创建一个观察者
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    
    runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                              kCFRunLoopAllActivities,
                                              YES,
                                              0,
                                              &runLoopObserverCallBack,
                                              &context);
    
    // 将观察者添加到主线程 runloop
    CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    
    // 创建子线程监控
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //子线程开启一个持续的loop用来进行监控
        while (YES) {
            long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, STUCKMONITORRATE * NSEC_PER_MSEC));
            if (semaphoreWait != 0) {
                if (!runLoopObserver) {
                    timeoutCount = 0;
                    dispatchSemaphore = 0;
                    runLoopActivity = 0;
                    return;
                }
                //两个runloop的状态，BeforeSources和AfterWaiting这两个状态区间时间能够检测到是否卡顿
                if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                    //出现三次出结果
                    if (++timeoutCount < 3) {
                        continue;
                    }
//                    NSLog(@"monitor trigger");
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                        NSString *stackStr = [SMCallStack callStackWithType:SMCallStackTypeMain];
                        SMCallStackModel *model = [[SMCallStackModel alloc] init];
                        model.stackStr = stackStr;
                        model.isStuck = YES;
                                            
                        NSLog(@"CPU useage overload thread stack：\n%@",stackStr);
                    });
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
        }// end while
        
        
    });
    
}

- (void)endMonitor {
    self.isMonitoring = NO;
    [self.cpuMonitorTimer invalidate];
    if (!runLoopObserver) {
        return;
    }
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    CFRelease(runLoopObserver);
    runLoopObserver = NULL;
}

#pragma mark - Private
- (void)updateCPUInfo {
//    NSLog(@"updateCPUInfo");
}

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
//    SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;
//    lagMonitor->runLoopActivity = activity;
    /*
     kCFRunLoopEntry = (1UL << 0),
     kCFRunLoopBeforeTimers = (1UL << 1),
     kCFRunLoopBeforeSources = (1UL << 2),
     kCFRunLoopBeforeWaiting = (1UL << 5),
     kCFRunLoopAfterWaiting = (1UL << 6),
     kCFRunLoopExit = (1UL << 7),
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     */
//    switch (activity) {
//        case kCFRunLoopEntry:
//            NSLog(@"CFRunLoopActivity = kCFRunLoopEntry");
//
//            break;
//        case kCFRunLoopBeforeTimers:
//            NSLog(@"CFRunLoopActivity = kCFRunLoopBeforeTimers");
//
//            break;
//        case kCFRunLoopBeforeSources:
//            NSLog(@"CFRunLoopActivity = kCFRunLoopBeforeSources");
//
//            break;
//        case kCFRunLoopBeforeWaiting:
//            NSLog(@"CFRunLoopActivity = kCFRunLoopBeforeWaiting");
//
//            break;
//        case kCFRunLoopAfterWaiting:
//            NSLog(@"CFRunLoopActivity = kCFRunLoopBeforeWaiting");
//
//            break;
//        case kCFRunLoopExit:
//            NSLog(@"CFRunLoopActivity = %kCFRunLoopExit");
//
//            break;
//        case kCFRunLoopAllActivities:
//
//            break;
//
//
//        default:
//            break;
//    }
//    dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
//    dispatch_semaphore_signal(semaphore);
}
@end

